<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="welcome" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    Mysql锁 |  ChenyyのBlog
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">
  
<link rel="stylesheet" href="/css/custom.css">

  
  <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-Mysql/Mysql锁"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Mysql锁
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/05/03/Mysql/Mysql%E9%94%81/" class="article-date">
  <time datetime="2021-05-03T12:21:58.000Z" itemprop="datePublished">2021-05-03</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/Mysql/">Mysql</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.9k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">17 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <ul>
<li>全局锁&amp;表锁&amp;行锁&amp;页锁</li>
<li>悲观锁&amp;乐观锁</li>
<li>共享锁&amp;排他锁&amp;意向锁</li>
<li>记录锁&amp;间隙锁&amp;临键锁</li>
</ul>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210503215600.png"></p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/Snipaste_2021-05-04_09-21-44.png"></p>
<span id="more"></span>

<h1 id="全局锁-amp-表锁-amp-行锁-amp-页锁"><a href="#全局锁-amp-表锁-amp-行锁-amp-页锁" class="headerlink" title="全局锁&amp;表锁&amp;行锁&amp;页锁"></a>全局锁&amp;表锁&amp;行锁&amp;页锁</h1><ul>
<li>表锁：开销小，加锁快；不会出现死锁；锁定力度大，发生锁冲突概率高，并发度最低</li>
<li>行锁：开销大，加锁慢；会出现死锁；锁定粒度小，发生锁冲突的概率低，并发度高</li>
<li>页锁：开销和加锁速度介于表锁和行锁之间；会出现死锁；锁定粒度介于表锁和行锁之间，并发度一般</li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th>行锁</th>
<th>表锁</th>
<th>页锁</th>
</tr>
</thead>
<tbody><tr>
<td>MyISAM</td>
<td></td>
<td>√</td>
<td></td>
</tr>
<tr>
<td>BDB</td>
<td></td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>InnoDB</td>
<td>√</td>
<td>√</td>
<td></td>
</tr>
</tbody></table>
<h2 id="全局锁"><a href="#全局锁" class="headerlink" title="全局锁"></a>全局锁</h2><p>全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法，命令是 Flush tables with read lock (FTWRL)。</p>
<p>当你需要让整个库处于只读状态的时候，可以使用这个命令，之后其他线程的以下语句会被阻塞:数据更新语句 (数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。</p>
<p><strong>全局锁的典型使用场景是，做全库逻辑备份。</strong></p>
<p>为什么不使用<strong>set global readonly=true</strong>的方式呢?</p>
<ul>
<li>一是，在有些系统中，readonly的值会被用来做其他逻辑，比如用来判断一个库是主库还是备库。</li>
<li>二是，在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开，那么MySQL会自动释放这个全局 锁，整个库回到可以正常更新的状态。而将整个库设置为readonly之后，如果客户端发生异常，则数据库就会一直保持 readonly状态，这样会导致整个库⻓时间处于不可写状态，⻛险较高。</li>
</ul>
<h2 id="表级锁"><a href="#表级锁" class="headerlink" title="表级锁"></a>表级锁</h2><p>表级锁是MySQL中锁定粒度最大的一种锁，表示对当前操作的整张表加锁，它实现简单，资源消耗较少，被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。</p>
<p>特点：开销小，加锁快；不会出现死锁；锁定粒度大，发出锁冲突的概率最高，并发度最低。</p>
<p><strong>MySQL表级锁有两种模式：表共享读锁（Table Read Lock）和表独占写锁（Table Write Lock）</strong>。</p>
<p>隐式上锁（默认）：</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select（共享锁）</span><br><span class="line">insert、update、delete（排他锁）</span><br></pre></td></tr></table></figure>

<p>显式上锁/解锁（手动）：</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">lock table table_name read;（共享锁）</span><br><span class="line">lock table table_name write;（排他锁）</span><br><span class="line">unlock table table_name;（解锁单表）</span><br><span class="line">unlock tables;（解锁所有表）</span><br></pre></td></tr></table></figure>

<p>排查：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">show open tables;（查看）</span><br><span class="line"></span><br><span class="line">分析</span><br><span class="line">mysql&gt; show status like &#x27;table%&#x27;;</span><br><span class="line">+----------------------------+---------+</span><br><span class="line">| Variable_name              | Value   |</span><br><span class="line">+----------------------------+---------+</span><br><span class="line">| Table_locks_immediate      | 100     |</span><br><span class="line">| Table_locks_waited         | 11      |</span><br><span class="line">+----------------------------+---------+</span><br><span class="line">table_locks_waited：因表级锁争用而等待的次数</span><br><span class="line">table_locks_immediate：产生表级锁定的次数</span><br></pre></td></tr></table></figure>





<h2 id="行级锁"><a href="#行级锁" class="headerlink" title="行级锁"></a>行级锁</h2><p>行级锁是Mysql中锁定粒度最细的一种锁，表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小，但加锁的开销也最大。</p>
<p>特点：开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁冲突的概率最低，并发度也最高。</p>
<p><strong>行级锁分为共享锁和排他锁</strong>。</p>
<p>InnoDB行锁是通过给索引上的索引项加锁来实现的，这一点MySQL与Oracle不同，后者是通过在数据块中对相应数据行加锁来实现的。</p>
<p>意味着：<strong>只有通过索引条件检索数据，InnoDB才使用行级锁，否则，InnoDB将使用表锁！</strong></p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504092527.png"></p>
<p>注意事项：</p>
<ul>
<li>在不通过索引条件查询的时候，InnoDB 使用的是表锁，而不是行锁。</li>
<li>由于 MySQL 的行锁是针对索引加的锁，不是针对记录加的锁，所以虽然是访问不同行的记录，但是如果是使用相同的索引键，是会出现锁冲突的。应用设计的时候要注意这一点。</li>
<li>当表有多个索引的时候，不同的事务可以使用不同的索引锁定不同的行。另外，无论是使用主键索引、唯一索引或普通索引，InnoDB 都会使用行锁来对数据加锁。</li>
<li>即便在条件中使用了索引字段，但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的。如果 MySQL 认为全表扫描效率更高，比如对一些很小的表，它 就不会使用索引。这种情况下 InnoDB 将使用表锁，而不是行锁。因此,在分析锁冲突时，别忘了检查 SQL 的执行计划，以确认是否真正使用了索引。</li>
</ul>
<h3 id="锁冲突"><a href="#锁冲突" class="headerlink" title="锁冲突"></a>锁冲突</h3><p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504092722.png"></p>
<p>窗口A先修改了id为3的用户信息后，还没有提交事务，此时窗口B再更新同一条记录，然后就提示Lock wait timeout exceeded; try restarting transaction ，由于窗口A迟迟没有提交事务，导致锁一直没有释放，就出现了锁冲突，而窗口B一直在等待锁，所以出现了超过锁定超时的警告了。</p>
<ul>
<li><p>行锁锁冲突概率低，并发性高，但是会有死锁的情况出现。</p>
</li>
<li><p>表锁的锁冲突几率特别高，但不会出现死锁的情况。</p>
</li>
</ul>
<p>可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show status like &#x27;InnoDB_row_lock%&#x27;;</span><br><span class="line">+-------------------------------+-------+</span><br><span class="line">| Variable_name                 | Value |</span><br><span class="line">+-------------------------------+-------+</span><br><span class="line">| InnoDB_row_lock_current_waits | 0     |</span><br><span class="line">| InnoDB_row_lock_time          | 0     |</span><br><span class="line">| InnoDB_row_lock_time_avg      | 0     |</span><br><span class="line">| InnoDB_row_lock_time_max      | 0     |</span><br><span class="line">| InnoDB_row_lock_waits         | 0     |</span><br><span class="line">+-------------------------------+-------+</span><br></pre></td></tr></table></figure>

<p>InnoDB 的行级锁定状态变量不仅记录了锁定等待次数，还记录了锁定总时长，每次平均时长，以及最大时长，此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明如下：<br>InnoDB_row_lock_current_waits：当前正在等待锁定的数量；<br>InnoDB_row_lock_time：从系统启动到现在锁定总时间长度；<br>InnoDB_row_lock_time_avg：每次等待所花平均时间；<br>InnoDB_row_lock_time_max：从系统启动到现在等待最常的一次所花的时间；<br>InnoDB_row_lock_waits：系统启动后到现在总共等待的次数；</p>
<p>还可以通过设置InnoDB Monitors 来进一步观察发生锁冲突的表、数据行，并分析锁争用的原因。具体方法如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; create table InnoDB_monitor(a INT) engine=InnoDB;</span><br></pre></td></tr></table></figure>

<p>然后就可以用下面的语句来进行查看：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show engine InnoDB status;</span><br></pre></td></tr></table></figure>

<p>监视器可以通过发出下列语句来停止查看：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; drop table InnoDB_monitor;</span><br></pre></td></tr></table></figure>

<p>设置监视器后，会有详细的当前锁等待的信息，包括表名、锁类型、锁定记录的情况等，便于进行进一步的分析和问题的确定。默认情况下每15秒会向日志中记录监控的内容，如果长时间打开会导致.err文件变得非常的巨大，所以用户在确认问题原因之后，要记得删除监控表以关闭监视器，或者通过使用“–console”选项来启动服务器以关闭写日志文件。</p>
<h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><p>指两个或者多个事务在同一资源上相互占用，并请求锁定对方占用的资源，从而导致恶性循环的现象。</p>
<table>
<thead>
<tr>
<th>事务A</th>
<th>事务B</th>
</tr>
</thead>
<tbody><tr>
<td>begin;<br />update t set k=k+1 where id=1</td>
<td>begin;</td>
</tr>
<tr>
<td></td>
<td>update t set k=k+1 where id=2</td>
</tr>
<tr>
<td>update t set k=k+1 where id=2</td>
<td></td>
</tr>
<tr>
<td></td>
<td>update t set k=k+1 where id=1</td>
</tr>
</tbody></table>
<p>事务A在等待事务B释放id=2的行锁，而事务B在等待事务A释放id=1的行锁。 事务A和事务B在互相等待对方的资源 释放，就是进入了死锁状态。</p>
<p>MyISAM中是不会产生死锁的，因为MyISAM总是一次性获得所需的全部锁，要么全部满足，要么全部等待。而在InnoDB中，锁是逐步获得的，就造成了死锁的可能。</p>
<p>在MySQL中，行级锁并不是直接锁记录，而是锁索引。当两个事务同时执行，一个锁住了主键索引，在等待其他相关索引。另一个锁定了非主键索引，在等待主键索引。这样就会发生死锁。</p>
<p>当出现死锁以后，有两种策略:</p>
<ul>
<li>一种策略是，直接进入等待，直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。</li>
<li>另一种策略是，发起死锁检测，发现死锁后，主动回滚死锁链条中的某一个事务，让其他事务得以继续执行。将参数 innodb_deadlock_detect设置为on，表示开启这个逻辑。</li>
</ul>
<p>产生条件：</p>
<ul>
<li><p>互斥</p>
</li>
<li><p>请求与保持</p>
</li>
<li><p>不可剥夺</p>
</li>
<li><p>循环等待</p>
</li>
</ul>
<p>解除死锁：</p>
<ul>
<li><p>查看：show engine innodb status;</p>
</li>
<li><p>查看是否锁表（show OPEN TABLES where In_use &gt; 0;），查看进程（show processlist），杀死进程（kill id）。</p>
</li>
<li><p>查看当前锁定的事务（SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;）和等待的事务（SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;），杀死进程（kill id）。</p>
</li>
</ul>
<p>避免死锁：</p>
<p>1、加锁顺序一致。如果不同程序会并发存取多个表，尽量约定以相同的顺序访问表，可以大大降低死锁机会。</p>
<p>2、尽可能一次锁定所需数据行。在同一个事务中，尽可能做到一次锁定所需要的所有资源，减少死锁产生概率；</p>
<p>3、使用较低的隔离级别。对于非常容易产生死锁的业务部分，可以尝试使用升级锁定颗粒度，通过表级锁定来减少死锁产生的概率。</p>
<p>4、合理使用索引，减少不必要的索引。</p>
<p>5、保持简短的事务，单次操作数量不宜过多。</p>
<h2 id="页级锁"><a href="#页级锁" class="headerlink" title="页级锁"></a>页级锁</h2><p>页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快，但冲突多，行级冲突少，但速度慢。所以取了折衷的页级，一次锁定相邻的一组记录。BDB支持页级锁。</p>
<p>特点：开销和加锁时间界于表锁和行锁之间；会出现死锁；锁定粒度界于表锁和行锁之间，并发度一般。</p>
<h1 id="悲观锁-amp-乐观锁"><a href="#悲观锁-amp-乐观锁" class="headerlink" title="悲观锁&amp;乐观锁"></a>悲观锁&amp;乐观锁</h1><h2 id="悲观锁-Pessimistic-Locking"><a href="#悲观锁-Pessimistic-Locking" class="headerlink" title="悲观锁(Pessimistic Locking)"></a>悲观锁(Pessimistic Locking)</h2><p>悲观锁(Pessimistic Locking)，悲观锁是指在数据处理过程，使数据处于锁定状态，一般使用数据库的锁机制实现。</p>
<p>现在互联网高并发的架构中，受到fail-fast思路的影响，悲观锁已经非常少见了。</p>
<p><strong>思考：表锁、行锁、页锁是悲观锁吗？</strong></p>
<p>MySQL默认使用自动提交，使用悲观锁，必须关闭MySQL的自动提交。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">set</span> autocommit=0;</span><br></pre></td></tr></table></figure>

<p><strong>select…for update</strong>是MySQL提供的实现悲观锁的方式，可以保证当前的数据不会被其它事务修改。</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//0.开始事务</span></span><br><span class="line">begin; </span><br><span class="line"><span class="comment">//1.查询出商品库存信息</span></span><br><span class="line"><span class="keyword">select</span> stock <span class="keyword">from</span> tb <span class="keyword">where</span> id=<span class="number">1</span> <span class="keyword">for</span> update;</span><br><span class="line"><span class="comment">//2.扣减商品库存</span></span><br><span class="line">update tb <span class="keyword">set</span> stock=stock<span class="number">-1</span> <span class="keyword">where</span> id = <span class="number">1</span>;</span><br><span class="line"><span class="comment">//3.提交事务</span></span><br><span class="line">commit;</span><br></pre></td></tr></table></figure>



<p>注意事项：</p>
<ul>
<li>不带for update仍能正常查询出数据</li>
<li>必须要开启事务，确保执行完所有操作之后才释放掉锁。否则第一步查询操作后事务就提交释放锁了。</li>
<li>扫描过的行都会被锁上（行锁），确保select…for update走索引，否则全表扫描会将整个表锁住（表锁）。</li>
<li>Oracle还存在了select … for update nowait，即发现被锁后不等待，立刻报错。</li>
</ul>
<h2 id="乐观锁（Optimistic-Lock）"><a href="#乐观锁（Optimistic-Lock）" class="headerlink" title="乐观锁（Optimistic Lock）"></a>乐观锁（Optimistic Lock）</h2><p>乐观锁认为数据一般情况下不会造成冲突，所以在数据进行提交更新的时候，才会正式对数据的冲突与否进行检测。</p>
<p>一般利用数据版本号（<strong>version</strong>）/<strong>时间戳</strong>机制实现。</p>
<p>数据库表增加一个数字类型的 “version” 字段，读取数据时同时读取version字段，更新数据时对<strong>version值+1</strong>。提交更新时，判断数据库version值与第一次读取的version值是否相等，相等予以更新，否则更新失败。</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1.查询出商品库存信息</span></span><br><span class="line"><span class="keyword">select</span> (stock,version) <span class="keyword">from</span> tb <span class="keyword">where</span> id=<span class="number">1</span>;</span><br><span class="line"><span class="comment">//2.扣减商品库存</span></span><br><span class="line">update tb <span class="keyword">set</span> stock=stock<span class="number">-1</span>,version=version+<span class="number">1</span> <span class="keyword">where</span> id=<span class="number">1</span> <span class="keyword">and</span> version=<span class="meta">#&#123;version&#125;;</span></span><br></pre></td></tr></table></figure>



<p>采用version号作为乐观锁，则每次只有一个事务能更新成功，业务感知上就是大量操作失败。优化后的sql减小锁力度，从而提升吞吐。</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">update tb <span class="keyword">set</span> stock=stock<span class="number">-1</span> <span class="keyword">where</span> id = <span class="number">1</span> <span class="keyword">and</span> stock<span class="number">-1</span>&gt;=<span class="number">0</span></span><br></pre></td></tr></table></figure>



<p>注意事项：</p>
<ul>
<li>读写分离需要强制读主库，或者把select语句放在事务中，查询的就是主库。</li>
<li>若行数据中存在多字段并发更新的场景，会因为version的失败而导致非常高的失败率。可以针对单字段，设计该字段版本号。若变更太频繁，可以提出来单独维护，做到冷热数据分离。</li>
</ul>
<h1 id="共享锁-amp-排他锁-amp-意向锁"><a href="#共享锁-amp-排他锁-amp-意向锁" class="headerlink" title="共享锁&amp;排他锁&amp;意向锁"></a>共享锁&amp;排他锁&amp;意向锁</h1><h2 id="共享锁（shared-locks，S锁）"><a href="#共享锁（shared-locks，S锁）" class="headerlink" title="共享锁（shared locks，S锁）"></a>共享锁（shared locks，S锁）</h2><p>共享锁又叫读锁（shared locks，S锁），语法如下：</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> … <span class="keyword">lock</span> <span class="keyword">in</span> share mode;</span><br></pre></td></tr></table></figure>

<ul>
<li>事务加上S锁后只能读数据，不能修改</li>
<li>其它事务只能再加S锁，不能加其它锁</li>
</ul>
<h2 id="排他锁（exclusive-locks，X锁）"><a href="#排他锁（exclusive-locks，X锁）" class="headerlink" title="排他锁（exclusive locks，X锁）"></a>排他锁（exclusive locks，X锁）</h2><p>排它锁又叫写锁（exclusive locks，X锁），语法跟悲观锁一样：</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ... <span class="keyword">for</span> update </span><br></pre></td></tr></table></figure>

<ul>
<li>获得X锁的事务既能读数据，也能修改数据</li>
<li>其它事务不能加任何类型的锁，直到X锁释放。</li>
</ul>
<h2 id="意向锁-Intention-Locks"><a href="#意向锁-Intention-Locks" class="headerlink" title="意向锁(Intention Locks)"></a>意向锁(Intention Locks)</h2><p>InnoDB中的S锁和X锁是行锁，当事务需要遍历所有行的锁持有情况，首先需要判断表A是否有表级锁，然后再判断表A每一行是否有行级锁，性能较低。</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM A <span class="keyword">for</span> update; (加X锁)</span><br></pre></td></tr></table></figure>



<p>意向锁(Intention Locks)是表锁，多用在innoDB中，是由数据库自己维护的加锁标识，可以快速判断表中是否有记录被上锁，避免遍历，提高加锁效率。</p>
<p>意向锁的主要作用是提升存储引擎性能，检查行锁前先检查意向锁是否存在，如果存在则阻塞线程。</p>
<p>意向锁分为意向共享锁(IS锁)和意向排它锁(IX锁)</p>
<ul>
<li>IS锁：表示事务中将要对某些行加S锁</li>
<li>IX锁：表示事务中将要对某些行加X锁</li>
</ul>
<p>意向锁协议</p>
<ul>
<li>事务要获取表A某些行的S锁必须要获取表A的IS锁</li>
<li>事务要获取表A某些行的X锁必须要获取表A的IX锁</li>
</ul>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504155030.png"></p>
<h1 id="记录锁-amp-间隙锁-amp-临键锁"><a href="#记录锁-amp-间隙锁-amp-临键锁" class="headerlink" title="记录锁&amp;间隙锁&amp;临键锁"></a>记录锁&amp;间隙锁&amp;临键锁</h1><h2 id="记录锁（Record-Lock）"><a href="#记录锁（Record-Lock）" class="headerlink" title="记录锁（Record Lock）"></a>记录锁（Record Lock）</h2><p>记录锁锁的是表中的某一条记录，记录锁的出现条件<strong>必须是精准命中索引并且索引是唯一索引</strong>，如主键id</p>
<h2 id="间隙锁（Gap-Lock）"><a href="#间隙锁（Gap-Lock）" class="headerlink" title="间隙锁（Gap Lock）"></a>间隙锁（Gap Lock）</h2><p>间隙锁又称之为区间锁，每次锁定都是锁定一个区间，隶属行锁。</p>
<p>当我们查询数据用范围查询或者相等条件查询时，<strong>查询条件命中索引</strong>，请求共享或排他锁并且<strong>没有查询到符合条件的记录</strong>，此时就会将查询条件中的范围数据进行锁定(即使是范围库中不存在的数据也会被锁定)。对于键值在条件范围内但并不存在的记录，称为间隙，InnoDB也会为这些间隙加锁，即间隙锁。</p>
<p>比如数据库中只有1，4两条数据，如果查找不存在的3，则会导致（1，4）区间锁定。</p>
<p>在窗口A中我们根据id做一个范围更改操作，不提交事务，然后在范围B中插入一条记录，该记录的id值位于窗口A中的条件范围内。程序报错：Lock wait timeout exceeded; try restarting transaction 。锁定的区间就是 (1,3]这个<strong>左开右闭</strong>的区间。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504093811.png"></p>
<p>间隙锁只会出现在可重复读的事务隔离级别中，mysql5.7默认就是可重复读。</p>
<p>间隙锁的目的是为了防止幻读，防止其他事务在间隙中执行insert语句，或者将已有数据update到间隙中，保障数据的恢复和复制。</p>
<table>
<thead>
<tr>
<th>隔离级别</th>
<th>脏读（Dirty Read）</th>
<th>不可重复读（NonRepeatable Read）</th>
<th>幻读（Phantom Read）</th>
</tr>
</thead>
<tbody><tr>
<td>未提交读（Read uncommitted）</td>
<td>可能</td>
<td>可能</td>
<td>可能</td>
</tr>
<tr>
<td>已提交读（Read committed）</td>
<td>不可能</td>
<td>可能</td>
<td>可能</td>
</tr>
<tr>
<td>可重复读（Repeatable read）</td>
<td>不可能</td>
<td>不可能</td>
<td>可能</td>
</tr>
<tr>
<td>可串行化（Serializable ）</td>
<td>不可能</td>
<td>不可能</td>
<td>不可能</td>
</tr>
</tbody></table>
<p>在InnoDB下，间隙锁的产生需要满足三个条件：</p>
<ul>
<li>隔离级别为可重复读（Repeatable read）</li>
<li>当前读</li>
<li>查询条件能够走到索引</li>
</ul>
<h2 id="临键锁（Next-key-Lock）"><a href="#临键锁（Next-key-Lock）" class="headerlink" title="临键锁（Next-key Lock）"></a>临键锁（Next-key Lock）</h2><p>mysql的行锁默认就是使用的临键锁，临键锁是由记录锁和间隙锁共同实现的。</p>
<p>间隙锁的触发条件是命中索引，范围查询或等值查询没有匹配到相关记录。而临键锁恰好相反，临键锁的触发条件也是<strong>查询条件命中索引</strong>，不过，临键锁<strong>有匹配到数据库记录</strong>。</p>
<p>间隙锁所锁定的区间是一个左开右闭的集合，而临键锁锁定是当前记录的区间和下一个记录的区间。</p>
<p>现在数据库中只有三条数据1、5、7，当修改范围为1~8时，则锁定的区间为(1,+∞)，锁定额不单是查询范围，并且还锁定了当前范围的下一个范围区间，此时，查询的区间8，在数据库中是一个不存在的记录值，并且，如果此时的查询条件是小于或等于8，也是一样的锁定8到后面的区间。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504095039.png"></p>
<p>现在数据库有三条数据id分别是1、5、7，查询条件改为大于1小于7。由于7在数据库中是已知的记录，所以此时的锁定后，只锁定了(1,7]，7之后的数据都没有被锁定。我们还是可以正常插入id为8的数据及其后面的数据。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210504095308.png"></p>
<p>所以，临键锁锁定区间和查询范围后匹配值很重要，如果后匹配值存在，则只锁定查询区间，否则锁定查询区间和后匹配值与它的下一个值的区间。</p>
<p>为什么临键锁后匹配会这样呢？</p>
<p>mysql的索引是基于B+树实现的，每个树节点上都有多个元素，即关键字数，当我们的索引树上只有1、5、7时，我们查询1~8，这个时候由于树节点关键字中并没有8，所以就把8到正无穷的区间范围都给锁定了。</p>
<p>如果数据库中id有1、5、7、10，此时我们再模糊匹配id为1~8的时候，由于关键字中并没有8，所以找比8大的，也就找到了10，根据左开右闭原则，此时10也是被锁定的，但是id为11的记录还是可以正常进行插入的。</p>
<p>参考：</p>
<p>探索Mysql锁机制(一)乐观锁&amp;悲观锁：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/ed896335b3b4">https://www.jianshu.com/p/ed896335b3b4</a></p>
<p>探索Mysql锁机制(二)共享锁&amp;排他锁&amp;意向锁&amp;间隙锁：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/904f52bde904">https://www.jianshu.com/p/904f52bde904</a></p>
<p>MySQL中锁详解（行锁、表锁、页锁、悲观锁、乐观锁等）：<a target="_blank" rel="noopener" href="https://www.cnblogs.com/jpfss/p/8890250.html">https://www.cnblogs.com/jpfss/p/8890250.html</a></p>
<p>深入理解数据库行锁与表锁：<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/52678870">https://zhuanlan.zhihu.com/p/52678870</a></p>
<p>MySQL中的行级锁,表级锁,页级锁：<a target="_blank" rel="noopener" href="https://www.hollischuang.com/archives/914">https://www.hollischuang.com/archives/914</a></p>
<p>Mysql表锁、行锁、页锁：<a target="_blank" rel="noopener" href="https://www.bbsmax.com/A/MAzAn0xpJ9/">https://www.bbsmax.com/A/MAzAn0xpJ9/</a></p>
<p>如何基于MySQL的行锁来实现悲观锁？：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/512d877dff39">https://www.jianshu.com/p/512d877dff39</a></p>
<p>MySQL锁：<a target="_blank" rel="noopener" href="https://processon.com/view/6038a4566376896cd60a0e7a?fromnew=1#outline">https://processon.com/view/6038a4566376896cd60a0e7a?fromnew=1#outline</a></p>
<p>面试问题（mysql是行级锁还是表级锁）：<a target="_blank" rel="noopener" href="https://blog.csdn.net/hxpjava1/article/details/79407961">https://blog.csdn.net/hxpjava1/article/details/79407961</a></p>
<p>mysql面试必问的东西mysql锁：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/ef2779b62980">https://www.jianshu.com/p/ef2779b62980</a></p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2021/05/03/Mysql/Mysql%E9%94%81/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Mysql/" rel="tag">Mysql</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/05/05/framework/Spring%20Batch%20%E6%89%B9%E5%A4%84%E7%90%86/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Spring Batch 批处理
          
        </div>
      </a>
    
    
      <a href="/2021/05/03/Java/Spring%20IOC%E5%8E%9F%E7%90%86/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Spring IOC原理</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> Chenyy
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="ChenyyのBlog"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">文章</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/book">收藏</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about/me">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>